-- UISkyCityCombat
-- Create by chendh
-- 天空之城战斗界面

-- UISkyCityCombat继承自Layer
UISkyCityCombat = class("UISkyCityCombat", function()
    return cc.Layer:create();
end);

function UISkyCityCombat.create(battle)
    return UISkyCityCombat.new(battle);
end

-- 最大附件数量
local MAX_ATTACHMENT_NUM    = 10;
local MAX_PET_NUM           = 40;

local SHIPSCALE             = 0.8;

-- 构造函数
function UISkyCityCombat:ctor(battle)
    -- 初始化
    self:setName("UISkyCityCombat");
    local node = cc.CSLoader:createNode("layout/sky_city/SkyCityCombat.csb");
    self:addChild(node);
    self.node = node;
    self.battle = battle;
    self.attacker = battle.attacker;
    self.defenser = battle.defenser;

    if type(battle.extraData) == "table" then
        self.activityId = battle.extraData.activity_id;
        self.combatId = battle.extraData.combat_id;
        self.revive = battle.extraData.revive;
    end

    self:setLocalZOrder(UIMgr.TOP_MENU_ZORDER + 1);

    cc.SpriteFrameCache:getInstance():addSpriteFrames("images/ui/hero_sky_city.plist");

    -- 注册事件处理回调函数
    self:registerEventCallback();

    -- 注册点击事件
    self:registerTouchEvent();

    -- 收集战斗双方数据
    self:collectData();

    -- 需要重绘下层级
    self:sortZnode();

    -- 重绘
    self:redraw();

    -- 适配
    self:resize();

    -- 播放开场效果
    self:playStartEffect();
end

-- 适配
function UISkyCityCombat:resize()
    local node = self.node;

    -- 顶部居中
    AlignM.alignToTopCenter(node, "title");

    AlignM.alignToBottomCenter(node, "BT");

    local skyNode       = findChildByName(self.node, "sky");
    local attackNode    = findChildByName(self.node, "attack_node");
    local defenseNode   = findChildByName(self.node, "defense_node");
    local attackEffect  = findChildByName(self.node, "attack_effect");
    local defenseEffect = findChildByName(self.node, "defense_effect");
    local skyEffectNode = findChildByName(self.node, "sky_effect");
    local bossCloudNode = findChildByName(self.node, "boss_cloud");

    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    local wScale = w / DESIGN_WIDTH;
    local scale;
    if w / h > DESIGN_WIDTH / DESIGN_HEIGHT then
        -- 比标准的宽
        scale = h / DESIGN_HEIGHT;
    elseif w / h <= DESIGN_WIDTH / DESIGN_HEIGHT then
        -- 标准比例或者偏高
        scale = w / DESIGN_WIDTH;
    end

    skyNode:setPosition(cc.p(w / 2, h / 2));
    skyNode:setScaleX(w / DESIGN_WIDTH);
    skyNode:setScaleY(h / DESIGN_HEIGHT);

    attackNode:setPosition(w / 4, h / 2);
    defenseNode:setPosition(w * 3 / 4, h / 2);

    attackEffect:setPosition(w / 2, h / 2);
    defenseEffect:setPosition(w / 2, h / 2);

    attackNode:setScale(scale * SHIPSCALE);
    defenseNode:setScale(scale * SHIPSCALE);

    attackEffect:setScale(scale);
    defenseEffect:setScale(scale);

    skyEffectNode:setPosition(w / 2, h / 2);

    bossCloudNode:setPosition(w / 2, h / 2);
end

-- 播放开场效果
function UISkyCityCombat:playStartEffect()
    local attackNode = findChildByName(self.node, "attack_node");
    local defenseNode = findChildByName(self.node, "defense_node");
    local attackTitle = findChildByName(self.node, "title/attack_info");
    local defenseTitle = findChildByName(self.node, "title/defense_info");
    local roundNode = findChildByName(self.node, "title/round_bg");

    -- 要飞入先隐藏起来
    attackTitle:setVisible(false);
    defenseTitle:setVisible(false);
    roundNode:setVisible(false);

    -- 攻守双方的title从两侧飞入
    local function titleFlyIn()
        local len = DESIGN_WIDTH / 2;

        -- 攻击方的title
        attackTitle:setVisible(true);
        attackTitle:setPositionX(attackTitle:getPositionX() - len);

        local moveBy1 = cc.EaseIn:create(cc.MoveBy:create(0.3, cc.p(len + 20, 0)), 5);
        local moveBy2 = cc.MoveBy:create(0.1, cc.p(-20, 0));
        local callFunc = cc.CallFunc:create(function ()
            attackTitle:stopAllActions();
        end);

        attackTitle:runAction(cc.Sequence:create(moveBy1, moveBy2, callFunc));

        -- 防守方的title
        defenseTitle:setVisible(true);
        defenseTitle:setPositionX(defenseTitle:getPositionX() + len);

        local moveBy1 = cc.EaseIn:create(cc.MoveBy:create(0.3, cc.p(-20 - len, 0)), 5);
        local moveBy2 = cc.MoveBy:create(0.1, cc.p(20, 0));
        local callFunc = cc.CallFunc:create(function ()
            defenseTitle:stopAllActions();
        end);

        defenseTitle:runAction(cc.Sequence:create(moveBy1, moveBy2, callFunc));

        -- 回合数节点从小变大
        roundNode:setVisible(true);
        roundNode:setScale(0.1);
        local scaleTo1 = cc.ScaleTo:create(0.2, 1.3);
        local scaleTo2 = cc.ScaleTo:create(0.1, 1);
        local callFunc = cc.CallFunc:create(function ()
            roundNode:stopAllActions();

            local effectNode = findChildByName(self.node, "sky_effect");
            local worldPos = roundNode:convertToWorldSpaceAR(cc.p(0, 0));
            local targetPos = effectNode:convertToNodeSpace(worldPos);

            -- 播放闪屏光效
            playEffect(effectNode, 1711, targetPos.x, targetPos.y);
            playEffect(effectNode, 1710, targetPos.x, targetPos.y);

            -- 更新下回合，尝试弹出跳过按钮
            self:updateRound();
        end);

        roundNode:runAction(cc.Sequence:create(scaleTo1, scaleTo2, callFunc));
    end

    -- 攻击方从左侧飞入
    local function attackFlyIn()
        attackNode.initX = attackNode:getPositionX();
        attackNode:setPositionX(-1 * attackNode.initX);
        -- 上下浮动
        playFluctuateAnima(attackNode, 0.05, 2);

        local moveTo1 = cc.EaseInOut:create(cc.MoveTo:create(2, cc.p(attackNode.initX + 30, attackNode:getPositionY())), 2.5);
        local moveTo2 = cc.MoveTo:create(0.1, cc.p(attackNode.initX, attackNode:getPositionY()));
        local callFunc = cc.CallFunc:create(function ()
            attackNode:stopAllActions();

            -- 开始正常的浮动效果
            self:playFluctuateEffect();

            -- title飞入
            titleFlyIn();
        end);

        attackNode:runAction(cc.Sequence:create(moveTo1, callFunc, moveTo2));
    end

    -- 防守方从右侧飞入
    local function defenseFlyIn()
        defenseNode.initX = defenseNode:getPositionX();
        defenseNode:setPositionX(1 * defenseNode.initX + DESIGN_WIDTH);
        -- 上下浮动
        playFluctuateAnima(defenseNode, 0.05, 2);

        local moveTo1 = cc.EaseInOut:create(cc.MoveTo:create(2, cc.p(defenseNode.initX - 30, defenseNode:getPositionY())), 2.5);
        local moveTo2 = cc.MoveTo:create(0.1, cc.p(defenseNode.initX, defenseNode:getPositionY()));
        local callFunc = cc.CallFunc:create(function ()
            defenseNode:stopAllActions();

            -- 开始正常的浮动效果
            self:playFluctuateEffect();

            -- title飞入
            titleFlyIn();
        end);

        defenseNode:runAction(cc.Sequence:create(moveTo1, callFunc, moveTo2));
    end


    -- 开始title的入场动画
    attackFlyIn();

    -- 防守方直接开始悬浮
    local shipId = defenseNode.shipId;
    local islandType = defenseNode.islandType;

    if type(islandType) == "string" then
        if islandType == "Whirlpool" then
            self:playWhirlpoolCampEffect();
            self:playWhirlpoolFluctuate(defenseNode);
        elseif islandType == "SkyActivity" then
            self:playSkyActivityFluctuate(defenseNode);
        else
            if self.defenser.dbase:query("island_boss") then
                playFluctuateAnima(defenseNode, 1.5, 10);
            else
                -- 浮岛
                self:playIslandFluctuate(defenseNode);
            end
        end
    elseif self.defenser.dbase:query("relic_boss") then
        playFluctuateAnima(defenseNode, 1.5, 10);
    else
        -- 飞艇
        self:playAirShipFluctuate(defenseNode, shipId);
    end

    -- 开场效果播放结束后，战斗开始解析行为序列
    performWithDelay(self, function ()
        self.battle:nextAction();
    end, 3);
    AudioM.playFx("airship_fly");
end

-- 收集数据
function UISkyCityCombat:collectData()
    local data = {};

    local fighterList = {
        ["attacker"] = self.attacker;
        ["defenser"] = self.defenser;
    }

    -- 收集战斗双方的数据
    for style, fighter in pairs(fighterList) do
        local shipId = fighter.dbase:query("ship_id", 1);
        local fighterInfo = {
            ["id"]      = fighter.dbase:query("id"),
            ["max_hp"]  = fighter:queryAttrib("fight_ability"),
            ["cur_hp"]  = fighter:queryAttrib("hp"),
            ["fire"]    = fighter:queryAttrib("fire"),
            ["armor"]   = fighter:queryAttrib("armor"),
            ["speed"]   = fighter:queryAttrib("speed"),
            ["luck"]    = fighter:queryAttrib("luck"),
            ["name"]    = fighter:getName(),
            ["pets"]    = fighter:getArmy(),
            ["pet_costumes"] = fighter:getArmyCostumes();
            ["robots"]  = fighter:getRobots(),
            ["shipId"]  = shipId,
            ["shipDeckType"]    = NewAirShipM.queryDeck(NewAirShipM.query(shipId, "deck_id"), "deck_type"),
            ["islandType"]      = fighter.dbase:query("island_type"),
            ["relic_boss"]      = fighter.dbase:query("relic_boss"),
            ["sky_tower"]       = fighter.dbase:query("sky_tower"),
            ["island_boss"]     = fighter.dbase:query("island_boss");
            ["show_shape"]      = fighter.dbase:query("show_shape");
            ["hideInsland"]    = fighter.dbase:query("hide_insland") or 0,
            ["steam_machine"]  = fighter.dbase:query("steam_machine") or "",
        };

        data[style] = fighterInfo;
    end

    self.data = data;
end

-- 绘制顶部的双方属性和回合
function UISkyCityCombat:redrawRoundAndAttrib()
    -- 回合数
    local roundLable = findChildByName(self.node, "title/round_bg/round");
    TextStyleM.setTextStyle(roundLable, TextStyleM.TEXT_SIZE_MIDDLE2, TextStyleM.TEXT_COLOR_BEIGE, true, -4);
    roundLable:setString(self.battle:getRound());

    -- 攻击方和防守方
    local attackInfo = findChildByName(self.node, "title/attack_info");
    local defenseInfo = findChildByName(self.node, "title/defense_info");

    local nodeList = { ["attacker"] = attackInfo, ["defenser"] = defenseInfo, };
    for style, attribNode in pairs(nodeList) do
        local fighterInfo = self.data[style] or {};

        -- 绘制属性部分
        local abilityBar    = findChildByName(attribNode, "army_ability_bg/bar");
        local armyAbility   = findChildByName(attribNode, "army_ability_bg/army_ability");
        local fireLable     = findChildByName(attribNode, "attrib_bg/fire");
        local armorLable    = findChildByName(attribNode, "attrib_bg/armor");
        local speedLable    = findChildByName(attribNode, "attrib_bg/speed");
        local luckLable     = findChildByName(attribNode, "attrib_bg/luck");
        local nameLable     = findChildByName(attribNode, "army_ability_bg/name");

        -- 设置血条百分比
        local percent = fighterInfo["cur_hp"] * 100 / fighterInfo["max_hp"];
        if percent < 0 then
            percent = 0;
        end
        if percent > 100 then
            percent = 100;
        end
        abilityBar:setPercent(percent);

        -- 设置四维和总战力
        -- 系统字
        local textNodeList = {fireLable, armorLable, speedLable, luckLable};
        for _, node in pairs(textNodeList) do
            node:setFontSize(TextStyleM.TEXT_SIZE_TINY);
            node:setTextColor(TextStyleM.TEXT_COLOR_LIGHT_BLUE);
            TextStyleM.setOutlineStyle(node, nil, nil ,true);
        end
        armyAbility:setFontSize(TextStyleM.TEXT_SIZE_TINY);
        armyAbility:setTextColor(TextStyleM.TEXT_COLOR_BEIGE);
        TextStyleM.setOutlineStyle(armyAbility, nil, nil ,true);

        armyAbility:setString(changeNumString(fighterInfo["cur_hp"]));
        fireLable:setString(fighterInfo["fire"]);
        armorLable:setString(fighterInfo["armor"]);
        speedLable:setString(fighterInfo["speed"]);
        luckLable:setString(fighterInfo["luck"]);

        -- 将当前的四维和总战力数值记录在lastXXX里面，播放属性数值滚动的时候要用
        armyAbility.lastHp = fighterInfo["cur_hp"];
        fireLable.lastFire = fighterInfo["fire"];
        armorLable.lastArmor = fighterInfo["armor"];
        speedLable.lastSpeed = fighterInfo["speed"];
        luckLable.lastLuck = fighterInfo["luck"];

        -- 名字
        local nameText = fighterInfo["name"];

        -- 如果防守方是NPC，那么使用配置表中的name
        local id = fighterInfo["id"];
        if style == "defenser" then
            if type(self.activityId) == "number" and type(self.combatId) == "number" then
                nameText = SkyActivityM.queryCombat(self.combatId, "name");
            elseif type(id) == "number" and id > 0 and SkyExploreM.query(id, "rule") ~= "player" then
                if fighterInfo["sky_tower"] == 1 then
                    nameText = getLocStr(SkyTowerM.query(id, "name"));
                else
                    nameText = getLocStr(SkyExploreM.query(id, "name"));
                end
            end
        end

        if getLang() == "zh" or getLang() == "tw" then
            nameLable:setFontSize(TextStyleM.TEXT_SIZE_MINI);
            nameLable:setTextColor(TextStyleM.TEXT_COLOR_DARK_BROWN);
        else
            TextStyleM.setTextStyle(nameLable, TextStyleM.TEXT_SIZE_MINI, TextStyleM.TEXT_COLOR_DARK_BROWN, nil, nil, nil, nil, nil, TextStyleM.TEXT_SIZE_MINI);
        end

        nameLable:setString(nameText);
        --TextStyleM.setOutlineStyle(nameLable);
    end
end

-- 显隐岛屿
function UISkyCityCombat:showInsland(node, show)
    local islandNode = node:getChildByName("island_node");
    local islandTypeNode = islandNode:getChildByName("islandType");
    local island1 = findChildByName(islandTypeNode, "islands_1");
    local island2 = findChildByName(islandTypeNode, "islands_2");
    local island3 = findChildByName(islandTypeNode, "islands_3");

    island1:setVisible(show);
    island2:setVisible(show);
    island3:setVisible(show);
end

-- 创建甲板或者浮岛
function UISkyCityCombat:createDeckOrIsland(parent, info)
    local shipDeckNode = findChildByName(parent, "ship_deck");
    local islandNode = findChildByName(parent, "island_node");
    local petNode;
    local underPetNode;

    if type(info["islandType"]) == "string" then
        local node = cc.CSLoader:createNode(string.format("layout/sky_city/islands/%s.csb", info["islandType"]));

        node:setName("islandType");
        islandNode:addChild(node);

        -- 怪物节点在浮岛节点内
        petNode = findChildByName(node, "monster_node");

        -- 把怪物节点保存在战斗方节点上，方便使用
        parent.petNode = petNode;
        parent.islandType = info["islandType"];

        shipDeckNode:setVisible(false);
        islandNode:setVisible(true);

        if info["relic_boss"] or info["island_boss"] or 1 == info["hideInsland"] then
            -- 隐藏三个浮岛
            self:showInsland(parent, false);
        end

        if info["islandType"] == "Whirlpool" then
            self:updateWhirlpoolBg(node);
        end
    elseif type(info["shipDeckType"]) == "string" then
        -- 载入飞船加班的csb文件
        shipDeckNode:removeAllChildren();
        local node = cc.CSLoader:createNode(string.format("layout/sky_city/airship_deck/%s.csb", info["shipDeckType"]));
        node:setName("shipDeck");
        shipDeckNode:addChild(node);

        -- 如果是防守方，那么需要反向
        if parent.style == "defenser" then
            node:setScaleX(-1);
        end

        -- 冈布奥节点在飞艇甲板节点内
        petNode = findChildByName(node, "pet_node");
        underPetNode = findChildByName(node, "under_pet_node");
        -- 把冈布奥节点保存在战斗方节点上，方便使用
        parent.petNode = petNode;
        parent.underPetNode = underPetNode;
        parent.shipId = info["shipId"];

        shipDeckNode:setVisible(true);
        if islandNode then
            islandNode:setVisible(false);
        end

        -- 添加飞艇尾炎喷射光效
        local flameEffectNode = findChildByName(node, "flame_effect");
        playEffect(flameEffectNode, 1702);
    end
end

-- 绘制冈布奥
function UISkyCityCombat:redrawSlime(parent, index)
    local pet = findChildByName(parent.petNode, "pet" .. index);

    if not pet then
        pet = findChildByName(parent.underPetNode, "pet" .. index);
    end

    if not pet then
        return;
    end

    local petId;
    local fighterInfo = parent.fighterInfo;

    if index <= #fighterInfo["pets"] then
        -- 载入冈布奥图片
        petId = fighterInfo["pets"][index];
        local icon = PetM.getDefaultIcon(petId);

        local costumeId = fighterInfo["pet_costumes"][index] or 0;
        if costumeId > 0 and ServerStatusM.query(MODULE_SWITCH_OPEN_COSTUME) == 1 then
            local modelId = EquipM.queryPetModelID(costumeId);
            icon = PetM.queryModel(modelId, "icon");
        end

        pet:loadTexture(getHeroSkyCityIconPath(icon), 1);
    elseif index <= #fighterInfo["pets"] + #fighterInfo["robots"] and #fighterInfo["robots"] > 0 then
        -- 载入机器人图片
        petId = fighterInfo["robots"][index - #fighterInfo["pets"]];
        local iconIndex = SkyRobotM.query(petId, "icon");
        pet:loadTexture(getRobotSkyCityIconPath(iconIndex));
    else
        -- 如果不在出战列表中，那么移除掉
        pet:removeFromParent();
        return;
    end

    pet.petId = petId;
    pet.index = index;
    pet:setTag(index);

    -- 将锚点设置在底部居中，方便播放动画
    pet:setAnchorPoint(cc.p(0.5, 0));
    pet:setPositionY(pet:getPositionY() - pet:getContentSize().height / 2);

    return pet;
end

-- 绘制怪物
function UISkyCityCombat:redrawMonster(parent, index, monsterId, fighterInfo)
    local petNode = parent.petNode;
    local monster = findChildByName(petNode, "monster" .. index);

    if not monster then
        return;
    end

    -- 如果不在出战列表中，那么跳过
    if not monsterId or type(monsterId) ~= "number" then
        monster:setVisible(false);
        return;
    else
        monster:setVisible(true);
    end

    if fighterInfo["islandType"] == "SkyActivity" then
        self:createSlimeAsMonster(monster, monsterId, index, fighterInfo);
    else
        self:createMonster(monster, monsterId, index);
    end

    -- 更新下monster的位置
    self:updateMonsterPosition(monster, index);

    -- 创建投影
    self:redrawMonsterShadow(petNode, monsterId, fighterInfo, index);

    return monster;
end

-- 绘制怪物的投影
function UISkyCityCombat:redrawMonsterShadow(petNode, monsterId, fighterInfo, index)
    local monsterNode = findChildByName(petNode, "monster" .. index);

    -- 创建投影
    local shadowImg = ccui.ImageView:create(getLevelItemIconPath("shadow"));

    local offset = {0, 20};

    -- 调整阴影偏移
    local dbase;
    if DungeonBossM.isInBossLayer() then
        -- boss层天空战
        dbase = DungeonSkyBossM.query(monsterId, "dbase");
        offset = dbase["shadow_offset"] or {45, -5};
        shadowImg:setScale(1.5);
    elseif fighterInfo["sky_tower"] == 1 then
        -- 永恒之塔boss层
        dbase = SkyTowerM.query(fighterInfo["id"], "dbase");
        local isBoss = dbase["tower_boss"];

        if isBoss and 1 == isBoss then
            offset = dbase["shadow_offset"] or {45, -5};
            shadowImg:setScale(1.5);
        end
    end

    shadowImg:setPosition(monsterNode:getPositionX() + offset[1], monsterNode:getPositionY() + offset[2]);
    shadowImg:setLocalZOrder(-1);
    shadowImg:setName("shadow" .. index);
    petNode:addChild(shadowImg);

    -- 时空裂痕的怪物不显示投影
    if fighterInfo["islandType"] == "Whirlpool"
        or fighterInfo["relic_boss"]
        or fighterInfo["island_boss"] or
        monsterNode.effectSetting and 1 == monsterNode.effectSetting.hide_shadow then
        shadowImg:setVisible(false);
    end
end

-- 创建怪物模型
function UISkyCityCombat:createMonster(monster, monsterId, index)
    -- 创建怪物模型
    local monsterDisplay = MonsterDisplay.create(monsterId);
    --monsterDisplay.normalAnimation:pause();
    monsterDisplay:gotoIdle();

    -- 设置缩放比例
    local scale = MonsterM.query(monsterId, "model_scale");

    monsterDisplay:setScale(scale * 0.8);

    -- 设置偏移量
    local offsetX = MonsterM.query(monsterId, "offset_x") or 0;
    local offsetY = MonsterM.query(monsterId, "offset_y") or 0;
    -- TODO: 浮岛BOSS的处理
    if MonsterM.query(monsterId, "monster_type") == MONSTER_TYPE_BOSS then
        offsetX = offsetX + 40;
        offsetY = offsetY + 60;
    end

    local dbase = MonsterM.query(monsterId, "dbase") or {};
    if type(dbase["sky_combat_effect"]) == "table" then
        monster.effectSetting = dbase["sky_combat_effect"];
    end

    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));
    monster:addChild(monsterDisplay);
    monster.display = monsterDisplay;

    if self.defenser.dbase:query("relic_boss") or self.defenser.dbase:query("island_boss") then
        -- 有变红的话需要还原下颜色
        local childList = getAllChilds(monsterDisplay);

        for _, child in pairs(childList) do
            -- 图片变红
            setRedMode(child, false);
        end
    end

    -- 载入怪物图片
    --monster:loadTexture(getMonsterIconPath(MonsterM.query(monsterId, "icon")));
    monster.petId = monsterId;
    monster.index = index;
    monster.isMonster = true;
    monster:setTag(index);

    -- 将锚点设置在底部居中，方便播放动画
    monster:setAnchorPoint(cc.p(0.5, 0));
    monster:setPositionY(monster:getPositionY() - monster:getContentSize().height / 2);
end

-- 创建史莱姆模型作为怪物
function UISkyCityCombat:createSlimeAsMonster(monster, monsterId, index, fighterInfo)
    -- 创建怪物模型
    local monsterDisplay;
    local modelId = PetM.query(monsterId, "default_model");

    if fighterInfo["show_shape"] == 1 then
        local shapeList = ShapeChangeM.queryShapeIdByClassId(monsterId);

        if #shapeList > 0 then
            monsterDisplay = ShapeChangeDisplay.create(shapeList[1]);
        else

            monsterDisplay = HeroDisplay.create(modelId);
        end
    else
        monsterDisplay = HeroDisplay.create(modelId);
    end
    --monsterDisplay.normalAnimation:pause();
    monsterDisplay:gotoIdle();

    -- 设置缩放比例
    local scale = PetM.queryModel(modelId, "model_scale");

    monsterDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = PetM.queryModel(modelId, "offset_x") or 0;
    local offsetY = PetM.queryModel(modelId, "offset_y") or 0;

    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));
    monster:addChild(monsterDisplay);
    monster.display = monsterDisplay;

    if self.defenser.dbase:query("relic_boss") or self.defenser.dbase:query("island_boss") then
        -- 有变红的话需要还原下颜色
        local childList = getAllChilds(monsterDisplay);

        for _, child in pairs(childList) do
            -- 图片变红
            setRedMode(child, false);
        end
    end

    -- 载入怪物图片
    --monster:loadTexture(getMonsterIconPath(MonsterM.query(monsterId, "icon")));
    monster.petId = monsterId;
    monster.index = index;
    monster.isMonster = true;
    monster:setTag(index);

    -- 将锚点设置在底部居中，方便播放动画
    monster:setAnchorPoint(cc.p(0.5, 0));
    monster:setPositionY(monster:getPositionY() - monster:getContentSize().height / 2);
end

-- 重绘界面
function UISkyCityCombat:redraw()
    closeCommunicatingForm();

    -- 绘制顶部的双方属性和回合
    self:redrawRoundAndAttrib();

    -- 攻击方和防守方
    local attackNode = findChildByName(self.node, "attack_node");
    local defenseNode = findChildByName(self.node, "defense_node");

    self.attackerList = {};
    self.defenserList = {};
    local nodeList = { ["attacker"] = attackNode, ["defenser"] = defenseNode, };
    for style, armyNode in pairs(nodeList) do
        local fighterInfo = self.data[style] or {};

        armyNode.fighterInfo    = fighterInfo;
        armyNode.style          = style;
        armyNode.shipId         = fighterInfo["shipId"];
        armyNode.islandType     = fighterInfo["islandType"];
        armyNode.shipDeckType   = fighterInfo["shipDeckType"];

        -- 创建甲板或者浮岛
        self:createDeckOrIsland(armyNode, fighterInfo);

        -- 绘制冈布奥或怪物
        for index = 1, MAX_PET_NUM do
            repeat
                if type(fighterInfo["islandType"]) == "string" then
                    -- 绘制冈布奥
                    local monster = self:redrawMonster(armyNode, index, fighterInfo["pets"][index], fighterInfo);

                    if monster then
                        if fighterInfo["relic_boss"]  then
                            local monsterNode = findChildByName(armyNode.petNode, "monster" .. index);
                            monsterNode:setPosition(cc.p(monsterNode:getPositionX() + 50, monsterNode:getPositionY() - 170));

                            self:bossApearEffect(monster);
                        elseif fighterInfo["island_boss"] then
                            local monsterNode = findChildByName(armyNode.petNode, "monster" .. index);
                            monsterNode:setPosition(cc.p(monsterNode:getPositionX() + 70, monsterNode:getPositionY() - 170));
                            self:bossApearEffect(monster);
                        end

                        -- 添加到攻击方或者防守方列表
                        if style == "attacker" then
                            table.insert(self.attackerList, monster);
                        else
                            table.insert(self.defenserList, monster);
                        end
                    end
                elseif type(fighterInfo["shipDeckType"]) == "string" then
                    -- 绘制冈布奥
                    local pet = self:redrawSlime(armyNode, index);

                    if pet then
                        -- 添加到攻击方或者防守方列表
                        if style == "attacker" then
                            table.insert(self.attackerList, pet);
                        else
                            table.insert(self.defenserList, pet);
                        end
                    end
                end
            until true;
        end
    end

    -- 背景特效
    self:playBgEffect();

    -- 浮云特效
    self:palyCloudEffect();

    -- boss云层效果
    self:playSkyBossCloud();
end

-- 浮云特效
function UISkyCityCombat:palyCloudEffect()
    local skyEffectNode = findChildByName(self.node, "sky_effect");
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    math.randomseed(tostring(os.time()):reverse():sub(1, 6))

    local function cloudEffect()
        local cloudNum = math.random(1, 3);

        skyEffectNode:removeAllChildren();

        for i=1, cloudNum do
            local icon = ccui.ImageView:create();
            icon:loadTexture(getSkyCityCombatSkyIconPath("cloud1"), 1);
            icon:setPosition(cc.p(w, math.random(-1 * h / 2, h / 2)));

            local delay = cc.DelayTime:create(math.random(2000) / 300);
            local moveBy = cc.MoveBy:create(20, cc.p(-2 * w, 0));
            icon:runAction(cc.Sequence:create(delay, moveBy));

            skyEffectNode:addChild(icon);
        end

        performWithDelay(skyEffectNode, cloudEffect, math.random(30, 60));
    end

    performWithDelay(skyEffectNode, cloudEffect, math.random(5, 30));
end

-- 播放飞艇悬浮效果
function UISkyCityCombat:playAirShipFluctuate(node, shipId)
    local deckConfig = NewAirShipM.queryDeck(NewAirShipM.query(shipId, "deck_id"));
    local shipDeckNode = findChildByName(node, "ship_deck");
    local shipDeck = findChildByName(shipDeckNode, "shipDeck");
    local petNode = findChildByName(shipDeck, "pet_node");
    local underPetNode = findChildByName(shipDeck, "under_pet_node");

    -- 整个飞艇悬浮效果
    playFluctuateAnima(shipDeck, 1.5, 10);

    -- 不附加悬浮效果的飞艇的附件
    for index = 1, MAX_ATTACHMENT_NUM do
        if table.indexOf(deckConfig["no_suspend_attachment"], index) > 0 then
            local attachment = findChildByName(shipDeck, "attachment" .. index);

            if attachment then
                playFluctuateAnima(attachment, 1.5, -10);
            end
        end
    end

    -- 不附加悬浮效果的冈布奥
    for _, index in ipairs(deckConfig["no_suspend_pet_pos"]) do
        local pet = petNode:getChildByTag(index);

        if not pet then
            pet = underPetNode:getChildByTag(index);
        end

        if pet then
            -- 不需要移动的冈布奥反向悬浮
            playFluctuateAnima(pet, 1.5, -10);
        end
    end
end

-- 背景浮云飘动
function UISkyCityCombat:playBgEffect()
    local bgParent = findChildByName(self.node, "sky");
    if not bgParent then
        return;
    end

    local w = cc.Director:getInstance():getOpenGLView():getFrameSize().width;
    local h = cc.Director:getInstance():getOpenGLView():getFrameSize().height;
    local function cloudEffect(node)
        local originX = node:getPositionX();
        local moveBy1 = cc.MoveBy:create(80, cc.p(w, 0));
        local callFunc = cc.CallFunc:create(function() node:setPositionX(originX); end);

        node:runAction(cc.RepeatForever:create(cc.Sequence:create(moveBy1, callFunc, moveBy1)));
    end

    -- 获取背景浮云节点
    local childs = bgParent:getChildren();
    for _, child in pairs(childs) do
        local name = child:getName();
        if string.startWith(name, "cloud") then
            performWithDelay(self.node, function()
                cloudEffect(child);
            end, math.random(1, 3));
        end
    end
end

-- 漂浮效果
function UISkyCityCombat:monsterAnimaSelect(index)
    local fighterInfo = self.data["defenser"];
    if index == 4 and fighterInfo["steam_machine"]
        and fighterInfo["steam_machine"] == "steam_tank" then
        -- 4号位要调整一下
        return 2;
    end

    if table.indexOf({1, 4, 6}, index) > 0 then
        return 1;
    end

    return 2;
end

-- 播放浮岛悬浮效果
function UISkyCityCombat:playIslandFluctuate(node)
    local islandNode = node:getChildByName("island_node");
    local islandTypeNode = islandNode:getChildByName("islandType");
    local island1 = findChildByName(islandTypeNode, "islands_1");
    local island2 = findChildByName(islandTypeNode, "islands_2");
    local island3 = findChildByName(islandTypeNode, "islands_3");
    local petNode = findChildByName(islandTypeNode, "monster_node");

    -- 3个岛屿往不同方向悬浮移动
    playFluctuateAnima(island1, 1.5, -10);
    playFluctuateAnima(island2, 1.5, 10);
    playFluctuateAnima(island3, 1.5, -10);

    -- 岛屿上的怪物跟随自己站的岛屿进行悬浮
    for index = 1, 7 do
        local monster = petNode:getChildByTag(index);
        local shadow = petNode:getChildByName("shadow" .. index);

        if monster then
            if self:monsterAnimaSelect(index) == 1 then
                playFluctuateAnima(monster, 1.5, 10);

                if shadow then
                    playFluctuateAnima(shadow, 1.5, 10);
                end
            else
                playFluctuateAnima(monster, 1.5, -10);

                if shadow then
                    playFluctuateAnima(shadow, 1.5, -10);
                end
            end
        end
    end
end

-- 播放天空活动浮岛悬浮效果
function UISkyCityCombat:playSkyActivityFluctuate(node)
    local islandNode = node:getChildByName("island_node");
    local islandTypeNode = islandNode:getChildByName("islandType");
    local island1 = findChildByName(islandTypeNode, "islands_1");
    local island2 = findChildByName(islandTypeNode, "islands_2");
    local island3 = findChildByName(islandTypeNode, "islands_3");
    local island4 = findChildByName(islandTypeNode, "islands_4");
    local island5 = findChildByName(islandTypeNode, "islands_5");
    local monsterNode = findChildByName(islandTypeNode, "monster_node");

    -- 中间的浮岛和怪物浮岛同步悬浮
    playFluctuateAnima(island2, 1.5, 10);
    playFluctuateAnima(monsterNode, 1.5, 10);

    playFluctuateAnima(island1, 1.5, -10);
    playFluctuateAnima(island3, 1, 8);
    playFluctuateAnima(island4, 1.5, -10);
    playFluctuateAnima(island5, 1, 8);
end

-- 播放悬浮效果
function UISkyCityCombat:playFluctuateEffect()
    -- 攻击方
    local attackNode = findChildByName(self.node, "attack_node");
    -- 播放飞艇悬浮特效
    self:playAirShipFluctuate(attackNode, attackNode.shipId);

    -- 攻击方特效播放节点也需要悬浮下
    --local attackEffect = findChildByName(self.node, "attack_effect");
    --playFluctuateAnima(attackEffect, 1.5, 10);
end

-- 播放浮岛效果
function UISkyCityCombat:playWhirlpoolFluctuate(defenserNode)
    playFluctuateAnima(defenserNode, 3, -10);
end

-- 刷新回合数
function UISkyCityCombat:updateRound()
    local round = self.battle:getRound();
    local roundLable = findChildByName(self.node, "title/round_bg/round");

    local function delay()
        roundLable:setString(round);
    end

    if roundLable:getString() ~= tostring(round) then
        local scaleUp = cc.ScaleTo:create(0.2, 1.3);
        local scaleDown = cc.ScaleTo:create(0.1, 1);
        local fadeIn = cc.FadeIn:create(0.2);
        local fadeOut = cc.FadeOut:create(0.1);
        roundLable:runAction(cc.Sequence:create(scaleUp, scaleDown));
        roundLable:runAction(cc.Sequence:create(fadeOut, fadeIn));

        performWithDelay(self.node, delay, 0.1);
    else
        delay();
    end

    local btnBack = findChildByName(self.node, "BT/btn_back");
    local relicVideo = self.defenser.dbase:query("video");

    if round <= 1 and not relicVideo and ME.user.dbase:query("max_explore_level", 0) < 100 then
        btnBack:setVisible(false);
    elseif not btnBack:isVisible() and not self.delayAppear then
        self:btnBackAppear();
    end

    if self.battle:checkRefreshMonster() then
        btnBack:setTitleText(getLocStr("next_round"));
    else
        btnBack:setTitleText(getLocStr("end_sky_city_combat"));
    end
end

-- 跳过按钮出现效果
function UISkyCityCombat:btnBackAppear()
    local btnBack = findChildByName(self.node, "BT/btn_back");
    local initScale = btnBack:getScale();

    btnBack:setVisible(true);
    btnBack:setScale(0.1);
    btnBack:setOpacity(0);

    local scaleUp = cc.ScaleTo:create(0.2, 1.3 * initScale);
    local scaleDown = cc.ScaleTo:create(0.1, 1 * initScale);
    local fadeIn = cc.FadeIn:create(0.2);
    local fadeOut = cc.FadeOut:create(0.1);
    btnBack:runAction(cc.Sequence:create(scaleUp, scaleDown));
    btnBack:runAction(cc.Sequence:create(fadeOut, fadeIn));

    if self.battle:checkRefreshMonster() then
        btnBack:setTitleText(getLocStr("next_round"));
    else
        btnBack:setTitleText(getLocStr("end_sky_city_combat"));
    end
end

-- 刷新属性
function UISkyCityCombat:updateAttrib(fighter)
    -- 血条变化和血量数值滚动
    local function numScrolling(textNode, barNode, val, startVal, maxValue, speed, thousandType)
        local val = tonumber(val);
        local increaseVal = math.abs(val - startVal);
        local scrollNum = math.floor(increaseVal / speed);
        if scrollNum < 1 then
            scrollNum = 1;
        end

        local function scrolling()
            if thousandType then
                textNode:setString(changeNumString(startVal));
            else
                textNode:setString(tostring(startVal));
            end
            if startVal == val then
                return;
            end

            local inVal = math.abs(val - startVal);
            local num = math.min(inVal, scrollNum);

            if val > startVal then
                startVal = startVal + num;
            else
                startVal = startVal - num;
            end

            -- 血量进度条变化
            if barNode then
                local percent = startVal * 100 / maxValue;
                if percent < 0 then
                    percent = 0;
                end
                if percent > 100 then
                    percent = 100;
                end
                barNode:setPercent(percent);
            end

            performWithDelay(textNode, scrolling, 1 / speed);
        end

        scrolling();
    end

    -- 刷新战斗双方的战力和四维
    local style = fighter:getStyle();
    local attribNode;
    local fighter;

    if style == "attacker" then
        -- 攻击方
        attribNode = findChildByName(self.node, "title/attack_info");
        fighter = self.attacker;
    else
        -- 伤害方
        attribNode = findChildByName(self.node, "title/defense_info");
        fighter = self.defenser;
    end

    local abilityBar    = findChildByName(attribNode, "army_ability_bg/bar");
    local armyAbility   = findChildByName(attribNode, "army_ability_bg/army_ability");

    -- 血量数值变化
    numScrolling(armyAbility, abilityBar, fighter.dbase:query("hp"), armyAbility.lastHp, fighter.dbase:query("fight_ability"), 30, true);
    armyAbility.lastHp = fighter.dbase:query("hp");

    -- 四维变化
    local fireLable     = findChildByName(attribNode, "attrib_bg/fire");
    local armorLable    = findChildByName(attribNode, "attrib_bg/armor");
    local speedLable    = findChildByName(attribNode, "attrib_bg/speed");
    local luckLable     = findChildByName(attribNode, "attrib_bg/luck");

    numScrolling(fireLable, nil, fighter:queryAttrib("fire"), fireLable.lastFire, fighter:queryAttrib("fire"), 30);
    fireLable.lastFire = fighter:queryAttrib("fire");

    numScrolling(armorLable, nil, fighter:queryAttrib("armor"), armorLable.lastArmor, fighter:queryAttrib("armor"), 30);
    armorLable.lastArmor = fighter:queryAttrib("armor");

    numScrolling(speedLable, nil, fighter:queryAttrib("speed"), speedLable.lastSpeed, fighter:queryAttrib("speed"), 30);
    speedLable.lastSpeed = fighter:queryAttrib("speed");

    numScrolling(luckLable, nil, fighter:queryAttrib("luck"), luckLable.lastLuck, fighter:queryAttrib("luck"), 30);
    luckLable.lastLuck = fighter:queryAttrib("luck");
end

-- 注册事件处理回调函数
function UISkyCityCombat:registerEventCallback()
    -- 降低GL calls需要隐藏的界面
    local needHideForm  = {"UISkyCity", "UISkyCityRuins", "UIRevengeRecords", "UISkyRevengeMain",
        "UISkyRelicMain", "UIRelicUserInfo"};

    -- 界面析构后，需要清理下
    self:registerScriptHandler(function(eventType)
        if eventType == "exit" then
            EventMgr.removeAll("UISkyCityCombat");

            -- 显示天空之城相关界面及topmenu界面
            for _, formName in pairs(needHideForm) do
                local uiForm = UIMgr.getCurrentScene():getFormByName(formName);
                if uiForm then
                    uiForm:setVisible(true);
                end
            end

            local topMenu = UIMgr.getCurrentScene():getFormByName("UITopMenu");
            if topMenu and topMenu.need_show then
                topMenu:setVisible(true);
            end
        elseif eventType == "enter" then
            -- 隐藏天空之城相关界面及topmenu界面，以降低GL calls
            for _, formName in pairs(needHideForm) do
                local uiForm = UIMgr.getCurrentScene():getFormByName(formName);
                if uiForm then
                    uiForm:setVisible(false);
                end
            end

            local topMenu = UIMgr.getCurrentScene():getFormByName("UITopMenu");
            if topMenu then
                -- 如果隐藏时处于显示状态，那么标记一下
                if topMenu:isVisible() then
                    topMenu.need_show = true;
                end

                topMenu:setVisible(false);
            end
        end
    end);

    -- 注册获得焦点的回调
    EventMgr.register("UISkyCityCombat", event.FOCUS_CHANGED, function(para)
        if para["get"] == "UISkyCityCombat" then

        elseif para["lose"] == "UISkyCityCombat" then

        end
    end);

    -- 注册天空之城战斗攻击行为的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_ATTACK, function(para)
        -- 播放攻击动作
        local source = para["source"];
        -- 刷新下回合数
        self:updateRound();

        -- 播放攻击特效
        self:playAttackEffect(para);

        -- 刷新下属性
        -- 攻击敌方的时候有的也会对自己附加属性，所以这里需要刷新一下
        self:updateAttrib(source);
    end);

    -- 注册天空之城战斗受创行为的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_DAMAGE, function(para)
        -- 播放受创动作
        local target = para["target"];

        -- 刷新下属性
        self:updateAttrib(target);

        -- 播放下受创特效
        self:playDamagedEffect(para);

        -- 如果没有后续行为了，那么可以认为是战斗结束了
        if not self.battle:checkNextAction() then
            self:endCombat();
        end
    end);

    -- 注册天空之城战斗治疗行为的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_CURE, function(para)
        -- 播放治疗动作
        local source = para["source"];

        -- 刷新下属性
        self:updateAttrib(source);

        -- 播放下治疗特效
        self:playCureEffect(para);
    end);

    -- 注册天空之城战斗BUFF行为的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_BUFF, function(para)
        -- 播放BUFF动作
        local source = para["source"];

        -- 刷新下属性
        self:updateAttrib(source);

        -- 播放下BUFF特效
        self:playBuffEffect(para);
    end);

    -- 注册天空之城战斗DEBUFF行为的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_DEBUFF, function(para)
        -- 播放BUFF动作
        local target = para["target"];

        -- 刷新下属性
        self:updateAttrib(target);

        -- 播放下BUFF特效
        self:playDebuffEffect(para);
    end);

    -- 注册天空之城战斗结束的回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_END, function(para)
        -- 刷新下属性
        self:updateAttrib(self.attacker);
        self:updateAttrib(self.defenser);

        -- 播放战斗结束效果
        self:endCombat();
    end);

    -- 注册天空之城属性触发事件回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_PROP_EFFECT, function(para)
        -- 播放属性触发特效
        self:combatPropEffect(para);
    end);

    -- 注册天空之城怪物刷新回调
    EventMgr.register("UISkyCityCombat", event.SKY_COMBAT_RE_MONSTER_EFFECT, function(para)
        -- 怪物替换成新的
        self:playRefreshMonsterEffect(para);
    end);
end

-- 战斗结束
function UISkyCityCombat:endCombat()

    if self.isEnd then
        return;
    end

    self.isEnd = true;

    local result = self.battle.result;
    local w = cc.Director:getInstance():getOpenGLView():getFrameSize().width;

    -- 战败的一方持续播放爆炸效果，并且飞出屏幕外
    local armyNode;
    local len = w / 2;
    if result == 1 then
        -- 防守方战败
        armyNode = findChildByName(self.node, "defense_node");
    else
        -- 攻击方战败
        armyNode = findChildByName(self.node, "attack_node");
        len = w / -2;
    end

    -- 光效帧回调
    local function playLoseEffect()
        if type(armyNode.effectTimes) == "number" and armyNode.effectTimes > 15 then
            return;
        end

        -- 播放爆炸效果
        playEffect(armyNode, 1719);

        armyNode.effectTimes = 1 + (armyNode.effectTimes or 0);

        performWithDelay(armyNode, playLoseEffect, 1);
    end

    playLoseEffect();

    -- 战败方缓慢的移动出去
    if armyNode.islandType ~= "Whirlpool" then
        local moveBy = cc.MoveBy:create(15, cc.p(len, -400));
        armyNode:runAction(cc.Sequence:create(moveBy));
    else
        local function moveOutEFfect()
            -- 防止浮岛穿帮
            local islandNode = findChildByName(armyNode, "island_node");

            local WhirlpoolNode = findChildByName(islandNode, "islandType");
            local skyBg = findChildByName(WhirlpoolNode, "bg");

            -- local machineEffect = findChildByName(WhirlpoolNode, "machine_effect");

            local moveBy1 = cc.MoveBy:create(15, cc.p(-len, 400));
            skyBg:runAction(cc.Sequence:create(moveBy1));

            -- local moveBy2 = cc.MoveBy:create(15, cc.p(-len, -400));
            -- machineEffect:runAction(cc.Sequence:create(moveBy2));

            local moveBy = cc.MoveBy:create(15, cc.p(len, -400));
            armyNode:runAction(cc.Sequence:create(moveBy));
        end

        self:dieEffect();

        performWithDelay(self, moveOutEFfect, 2);
    end

    local function callback()
        if type(self.activityId) == "number" and
            type(self.combatId) == "number" and
            result == 0 then
            -- 天空活动战斗，询问是否复活
            self:askToRevive();
        else
            -- 关闭战斗结果界面的时候也关闭战斗界面
            closeFormByName("UISkyCityCombat");

            -- 如果战胜了，那么就干掉天空活动战斗信息界面
            if result == 1 then
                closeFormByName("UISkyActivityCombatInfo");
            end
        end
    end

    -- 打开战斗结果界面
    local uiForm = UIMgr.getCurrentScene():getFormByName("UISkyCityCombatResult");
    if not uiForm then
        require "game/ui/form/sky_city/UISkyCityCombatResult";

        local args = {};
        if self.defenser.dbase:query("video") then
            -- 是录像，根据玩家来播放结果
            result = iif(result == 1, 0, 1);
            args["video"] = true;
        end

        if self.defenser.dbase:query("sky_tower") == 1 then
            args["isSkyTower"] = true;
        end

        args["activityId"] = self.activityId;
        args["combatId"] = self.combatId;

        local uiDialog = UISkyCityCombatResult.create(result, self.battle.bonus, callback, args);
        UIMgr.getCurrentScene():addForm(uiDialog);
    end
end

-- 注册点击事件
function UISkyCityCombat:registerTouchEvent()
    --[[local skyImg = findChildByName(self.node, "sky");
    skyImg:setTouchEnabled(true);
    local function onBgClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            closeFormByName("UISkyCityCombat");
        end
    end
    skyImg:addTouchEventListener(onBgClick);]]

    -- 注册返回按钮点击事件
    local btnBack = findChildByName(self.node, "BT/btn_back");
    local function onBackClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            -- 关闭当前界面
            AudioM.playFx("ship_return");

            -- 去战场尝试结束战斗
            if self.battle:checkRefreshMonster() then
                -- 刷新下回合数
                self.battle:nextWaveMonster();
                sender:setVisible(false);
                self.delayAppear = true;

                local effectNode = findChildByName(self.node, "sky_effect");
                local roundNode = findChildByName(self.node, "title/round_bg");
                local worldPos = roundNode:convertToWorldSpaceAR(cc.p(0, 0));
                local targetPos = effectNode:convertToNodeSpace(worldPos);

                -- 播放闪屏光效
                playEffect(effectNode, 1711, targetPos.x, targetPos.y);
                playEffect(effectNode, 1710, targetPos.x, targetPos.y);
                self:updateRound();
            else
                self.battle:tryToEnd();
                -- 直接播放战斗结束效果
                self:endCombat();
            end
        end
    end
    AddBackEventHandler(self, btnBack, onBackClick);
    TextStyleM.setTextStyle(btnBack, TextStyleM.TEXT_SIZE_MIDDLE2, TextStyleM.TEXT_COLOR_BROWN_YELLOW, true);
    btnBack:setTitleText(getLocStr("end_sky_city_combat"));
    -- 先隐藏，第二回合才开始出现
    btnBack:setVisible(false);

    -- 录像直接跳出来
    if self.defenser.dbase:query("video") then
        local initScale = btnBack:getScale();

        btnBack:setVisible(true);
        btnBack:setScale(0.1);
        btnBack:setOpacity(0);

        local delay = cc.DelayTime:create(0.5);
        local scaleUp = cc.ScaleTo:create(0.2, 1.3 * initScale);
        local scaleDown = cc.ScaleTo:create(0.1, 1 * initScale);
        local fadeIn = cc.FadeIn:create(0.2);
        local fadeOut = cc.FadeOut:create(0.1);
        btnBack:runAction(cc.Sequence:create(delay, scaleUp, scaleDown));
        btnBack:runAction(cc.Sequence:create(delay, fadeOut, fadeIn));
    end
end

-- 播放攻击特效
function UISkyCityCombat:playAttackEffect(para)
    local source = para["source"];
    local target = para["target"];
    local skillId = para["skillId"];
    local args = para["args"];

    local attackNode;
    local defenseNode;
    if source:getStyle() == "attacker" then
        -- 攻击方攻击
        attackNode = findChildByName(self.node, "attack_node");
        defenseNode = findChildByName(self.node, "defense_node");
    else
        -- 防守方攻击
        attackNode = findChildByName(self.node, "defense_node");
        defenseNode = findChildByName(self.node, "attack_node");
    end

    --[[if type(SkyShipSkillM.query(skillId, "skil_effect")) ~= "number" then
        -- 播放攻击技能名称
        self:playSkillName(attackNode, skillId);
    else]]
        if SkyShipSkillM.query(skillId, "class") == SHIP_TRIGGER_SELF then
            -- 只需播放攻击技能名称的
            self:playSkillName(attackNode, skillId);
        elseif skillId == 0 or #SkyShipSkillM.query(skillId, "group") <= 0 then
            -- 普通攻击
            self:playPhysicAttackEffecy(attackNode, defenseNode, skillId, args);
        elseif skillId > 0 then
            -- 秘宝攻击
            self:playSkillAttackEffecy(attackNode, defenseNode, skillId, args);
        else
            print(string.format("播放天空之城战斗攻击特效时，skillId为%d，播放失败", skillId));
        end
    --end
end

-- 播放下受创特效
function UISkyCityCombat:playDamagedEffect(para)
    local target = para["target"];
    local skillId = para["skillId"];
    local damage = para["damage"];
    local args = para["args"];

    local armyNode;
    local list;

    if target:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");

        local _, effect = playEffect(armyNode, 1701);

        -- 播放受创光效时，攻击方需要将光效水平翻转一下
        local effectScale = effect:getScaleX();
        effect:setScaleX(-1 * effectScale);

        list = self.attackerList;
    else
        -- 防守方
        armyNode = findChildByName(self.node, "defense_node");

        local _, effect = playEffect(armyNode, 1701);

        list = self.defenserList;
    end

    -- 播放震动效果
    if self.defenser.dbase:query("relic_boss") or self.defenser.dbase:query("island_boss") then
        playShakeEffect(armyNode, 0.02, 8, 10);
    else
        playShakeEffect(armyNode, 0.03, 10, 8);
    end

    --
    if self.defenser.dbase:query("relic_boss") or self.defenser.dbase:query("island_boss") then
        for _, child in ipairs(list) do
            local display = child.display;
            if display then
                if not child.effectSetting or child.effectSetting["no_damage"] ~= 1 then
                    display:gotoDamaged();
                    print("播放受创动作");
                end
                self:displayBloodEffect(display, 1, 4);
            end
        end
    elseif type(armyNode.islandType) == "string" then
        for _, child in ipairs(list) do
            local display = child.display;
            if display then
                if not child.effectSetting or child.effectSetting["no_damage"] ~= 1 then
                    display:gotoDamaged();
                    print("播放受创动作");
                end
            end
        end
    else
        self:bloodEffect(target, 1, 7);
    end

    -- 闪烁
    --[[local list;
    if target:getStyle() == "attacker" then
        list = self.attackerList;
    else
        list = self.defenserList;
    end

    for _, child in ipairs(list) do
        local tintTo = cc.Blink:create(1, 5);
        child:runAction(cc.Sequence:create(tintTo));
    end]]

    -- 播放数值效果
    self:playNumberEffect(armyNode, damage, "damage");

    -- 播放怪物后倾
    local style = 2;
    if target:getStyle() == "attacker" then
        style = 1;
    end

    -- 遗迹boss
    if not self.defenser.dbase:query("relic_boss") and not self.defenser.dbase:query("island_boss")  then
        self:playPetDamagedAction(list, style);
    end
end

-- 播放史莱姆掉血效果（变红）
function UISkyCityCombat:bloodEffect(target, index, total)
    if index >= total then
        return;
    end

    local armyNode;
    local list;

    if target:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
        list = self.attackerList;
    else
        -- 防守方
        armyNode = findChildByName(self.node, "defense_node");
        list = self.defenserList;
    end

    for _, child in ipairs(list) do
        -- 图片变红
        setRedMode(child, index % 2 == 1);
    end

    performWithDelay(armyNode, function ()
        self:bloodEffect(target, index + 1, total);
    end, 0.2);
end

-- 播放下治疗特效
function UISkyCityCombat:playCureEffect(para)
    local source = para["source"];
    local skillId = para["skillId"];
    local args = para["args"];
    local cureValue = args["cure"];

    local armyNode;
    local ratio = 1;

    if source:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
    else
        armyNode = findChildByName(self.node, "defense_node");

        ratio = -1;
    end

    -- 播放治疗效果
    self:playSkyCombatEffect(source, "cure");

    -- 治疗血量数值效果
    self:playNumberEffect(armyNode, cureValue, "cure");
end

-- 播放下BUFF特效
function UISkyCityCombat:playBuffEffect(para)
    local source = para["source"];
    local effect = para["effect"];

    -- 播放buff效果
    self:playSkyCombatEffect(source, effect);
end

-- 播放下DEBUFF特效
function UISkyCityCombat:playDebuffEffect(para)
    local target = para["target"];
    local skillId = para["skillId"];
    local effect = para["effect"];

    local armyNode;
    local list;

    if target:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");

        list = self.attackerList;
    else
        armyNode = findChildByName(self.node, "defense_node");

        list = self.defenserList;
    end

    -- 播放debuff效果
    self:playSkyCombatEffect(target, effect);

    -- 冈布奥或者怪物的表现
    --[[if type(armyNode.islandType) == "string" then
        -- 怪物

    else
        -- 冈布奥
        self:playSlimeDebuffEffect(list);
    end]]
end

-- 冈布奥被释放了debuff技能时的表现
function UISkyCityCombat:playSlimeDebuffEffect(list)
    -- 先把列表分成3批
    local times = 5;
    local ret = {};
    local num = math.ceil(#list / 2);

    for i=1, times do
        local randomArr = randomArray(#list, math.min(#list, num));
        local perList = {}

        for i = 1, #randomArr do
            table.insert(perList, list[randomArr[i]]);
        end

        table.insert(ret, perList);
    end

    local function purpleEffect(index)
        if index == times + 2 then
            return;
        end

        if ret[index - 1] then
            for _, pet in ipairs(ret[index - 1]) do
                setPurpleMode(pet, false);
            end
        end

        if ret[index] then
            for _, pet in ipairs(ret[index]) do
                setPurpleMode(pet, true);
                local delay = math.random(10) / 100;
                self:slimeShake(pet, delay);
            end
        end

        performWithDelay(self, function ()
            purpleEffect(index + 1);
        end, 0.2);
    end
    purpleEffect(1);
end

-- 播放普通攻击特效
function UISkyCityCombat:playPhysicAttackEffecy(attackNode, defenseNode, skillId, args)
    local effectNode;
    local shipDeckNode = findChildByName(attackNode, "ship_deck");
    local shipDeck = findChildByName(shipDeckNode, "shipDeck");
    local shipId = attackNode.shipId;
    local islandType = attackNode.islandType;

    if attackNode:getName() == "attack_node" then
        effectNode = findChildByName(self.node, "attack_effect");
    else
        effectNode = findChildByName(self.node, "defense_effect");
    end

    if type(islandType) == "string" then
        -- 野怪，播放怪物攻击特效
        self:playMonsterAttack(attackNode, defenseNode);

        -- 播放攻击技能名称
        self:playSkillName(attackNode, skillId);
        return;
    end

    local deckConfig = NewAirShipM.queryDeck(NewAirShipM.query(shipId, "deck_id"));
    local attachment = shipDeck:getChildByName("attachment" .. deckConfig["attack_attachment"]);

    if not attachment then
        print("找不到攻击方的攻击附件，无法播放攻击特效");
        return;
    end

    local worldPos = attachment:convertToWorldSpaceAR(cc.p(0, 0));
    local targetPos = effectNode:convertToNodeSpace(worldPos);

    local function frameEventFunc(frame)
        if frame.getEvent and frame:getEvent() == "pilor" then
            local index = frame:getFrameIndex();

            if index == deckConfig["move_frame"] then
                local offset = 20;
                if attackNode:getName() == "defense_node" then
                    offset = -20;
                end

                local moveLeft = cc.EaseInOut:create(cc.MoveBy:create(0.1, cc.p(-1 * offset, 0)), 0.1);
                local moveRight = cc.MoveBy:create(0.1, cc.p(offset, 0));
                attackNode:runAction(cc.Sequence:create(moveLeft, moveRight));
            end
        end
    end

    -- 获取配置的攻击光效及偏移
    local effectId = deckConfig["attack_effect"];
    local effectOffset = deckConfig["effect_offset"];
    if attackNode:getName() == "attack_node" then
        -- 光效的effect表里配置的是光效本身对于原点的偏移量，这里需要偏移下炮口对炮台的偏移量
        local _, effect = playEffect(effectNode, effectId, targetPos.x + effectOffset[1], targetPos.y + effectOffset[2], frameEventFunc, SHIPSCALE);
    else
        -- 防守方的炮台是水平翻转过的，这里光效需要反向偏移2倍的光效对于原点的x轴偏移量，并且再x轴反向偏移下炮口对炮台的偏移量
        local offset     = EffectM.query(effectId, "off_set");
        local _, effect = playEffect(effectNode, effectId, targetPos.x - 2 * offset[1] - effectOffset[1], targetPos.y + effectOffset[2], frameEventFunc, SHIPSCALE);
        local effectScale = effect:getScaleX();
        effect:setScaleX(-1 * effectScale);
    end

    -- 播放攻击技能名称
    self:playSkillName(attackNode, skillId);
end

-- 冈布奥跳跃
function UISkyCityCombat:playSlimeJump(petNode, delay)
    local scaleX = petNode:getScaleX();
    local scaleY = petNode:getScaleY();
    local delay = cc.DelayTime:create(delay);
    local callFunc1 = cc.CallFunc:create(function ()
        playEffect(petNode, 1714, 40, 25);
        setHighLightMode(petNode, true);
    end);
    local callFunc2 = cc.CallFunc:create(function ()
        setHighLightMode(petNode, false);
    end);
    local callFunc3 = cc.CallFunc:create(function ()
        local _, effect = playEffect(petNode, 1716, 40, 40, nil, 0.5);
        local move = cc.EaseOut:create(cc.MoveBy:create(0.3, cc.p(0, -60)), 2.5);

        effect:runAction(cc.Sequence:create(move));
    end);
    local scaleDown = cc.ScaleTo:create(0.3, scaleX, 0.8 * scaleY);
    local scaleUp = cc.ScaleTo:create(0.2, scaleX, scaleY);
    local moveUp1 = cc.EaseOut:create(cc.MoveBy:create(0.3, cc.p(0, 60)), 2.5);
    local moveUp2 = cc.EaseOut:create(cc.MoveBy:create(0.5, cc.p(0, 20)), 2.5);
    local moveDown = cc.EaseIn:create(cc.MoveBy:create(0.3, cc.p(0, -80)), 2.5);
    local scaleDown2 = cc.ScaleTo:create(0.1, scaleX, 0.8 * scaleY);
    local scaleUp2 = cc.ScaleTo:create(0.2, scaleX, scaleY);

    petNode:runAction(cc.Sequence:create(delay, callFunc1, scaleDown, callFunc2, scaleUp, callFunc3, moveUp1, moveUp2, moveDown, scaleDown2, scaleUp2));
end

-- 播放秘宝攻击特效
function UISkyCityCombat:playSkillAttackEffecy(attackNode, defenseNode, skillId, args)
    local skillConfig = SkyShipSkillM.query(skillId);
    local group = skillConfig["group"];
    local petList = {};

    -- 触发秘宝的冈布奥跳跃
    local list = {};
    if attackNode:getName() == "attack_node" then
        list = self.attackerList;
    else
        list = self.defenserList;
    end

    -- 触发秘宝的冈布奥跳跃
    for _, pet in ipairs(list) do
        local index = table.indexOf(group, pet.petId);
        if index > 0 then
            self:playSlimeJump(pet, 0.15 * index);
        end
    end

    -- 跳跃结束后开始播放攻击特效和技能名称
    performWithDelay(attackNode, function ()
        -- 播放技能前摇效果
        playEffect(attackNode, 1712);

        -- 播放攻击技能名称
        self:playSkillName(attackNode, skillId);
    end, 0.8);
end

-- 播放怪物攻击特效
function UISkyCityCombat:playMonsterAttack(attackNode, defenseNode)
    local monsterList = {};
    if attackNode:getName() == "attack_node" then
        monsterList = self.attackerList;
    else
        monsterList = self.defenserList;
    end

    for _, icon in ipairs(monsterList) do
        local offset = 30;
        if attackNode:getName() == "defense_node" then
            offset = -30;
        end
        local moveLeft = cc.EaseOut:create(cc.MoveBy:create(0.2, cc.p(offset, 0)), 0.1);
        local moveRight = cc.MoveBy:create(0.2, cc.p(-1 * offset, 0));
        icon:runAction(cc.Sequence:create(moveLeft, moveRight));

        if icon.effectSetting and icon.display then
            if icon.effectSetting["attack"] == 1 then
                icon.display:gotoAttack();
            end
        end

        if icon.isMonster == true then
            -- 如果是怪物的话，投影也要跟着移动
            local index = icon.index;
            local parent = icon:getParent();
            local shadow = parent:getChildByName("shadow" .. index);

            local moveLeft2 = cc.EaseOut:create(cc.MoveBy:create(0.2, cc.p(offset, 0)), 0.1);
            local moveRight2 = cc.MoveBy:create(0.2, cc.p(-1 * offset, 0));
            shadow:runAction(cc.Sequence:create(moveLeft2, moveRight2));
        end
    end
end

-- 播放攻击技能名称
function UISkyCityCombat:playSkillName(node, skillId)
    local skillName = SkyShipSkillM.query(skillId, "name");
    local skillNameNode = findChildByName(node, "skill_name");
    local nameLable = findChildByName(skillNameNode, "name");

    -- 设置技能名称文本
    local shipId = node.shipId;
    local color = TextStyleM.TEXT_COLOR_DARK_BROWN;
    local configColor = SkyShipSkillM.query(skillId, "skill_name_color");

    if configColor == "red" then
        color = TextStyleM.TEXT_COLOR_DARK_RED;
    elseif configColor == "purple" then
        color = TextStyleM.SKILL_TEXT_COLOR_DARK;
    elseif configColor == "green" then
        color = TextStyleM.TEXT_COLOR_BRIGHT_GREEN;
    elseif configColor == "blue" then
        color = TextStyleM.TEXT_COLOR_BLUE;
    end
    TextStyleM.setTextStyle(nameLable, TextStyleM.TEXT_SIZE_MIDDLE, color);

    -- 如果是飞艇主炮，那么显示"攻击！"
    if SkyShipSkillM.query(skillId, "class") == SHIP_SKILL_GUN then
        skillName = getLocStr("airship_physics");
    end
    nameLable:setString(skillName);

    local nameFrame = findChildByName(skillNameNode, "frame");
    autoResizeBg(nameLable, nameFrame);

    -- 设置可见并且初始化透明度
    skillNameNode:stopAllActions();
    skillNameNode:setVisible(true)
    skillNameNode:setOpacity(255);

    -- 停留1秒后淡出1秒
    skillNameNode:setScale(0.1);
    local delay = cc.DelayTime:create(1);
    local fadeOut = cc.FadeOut:create(1);
    skillNameNode:runAction(cc.Sequence:create(delay, fadeOut));
    local scaleTo = cc.ScaleTo:create(0.2, 1);
    skillNameNode:runAction(cc.Sequence:create(scaleTo));

    -- 隐藏节点
    performWithDelay(skillNameNode, function ()
        skillNameNode:setVisible(false);
    end, 2);
end

-- 播放数值效果
function UISkyCityCombat:playNumberEffect(node, damage, style)
    if damage <= 0 then
        return;
    end

    local numNode = node:getChildByName("num_node");
    numNode:removeAllChildren();

    local damageStr = tostring(damage);
    local len = string.getWCharCount(damageStr);
    local fontSize = 45;
    local firstPosX = - fontSize * len / 2;
    local path = "images/ui/number/number9.png";

    if style == "cure" then
        path = "images/ui/number/number12.png";
    end

    for i = 1, len do
        local char = (string.getWChar(damageStr, i));
        local text = ccui.TextAtlas:create("./0123456789", path, 40, 85, ".");
        text:setString(char);
        text:setPositionX(firstPosX);
        text:setVisible(false);

        text:setPositionY((i - len/2) * 10);

        numNode:addChild(text);

        firstPosX = firstPosX + fontSize;

        local callFunc1 = cc.CallFunc:create(function()
                text:setVisible(true);
                text:setScale(0.1);
                text:setRotation(-15);
            end);
        local callFunc2 = cc.CallFunc:create(function()
                text:removeFromParent();
            end);

        local delay1 = cc.DelayTime:create(0.13 * (i - 1));
        local delay2 = cc.DelayTime:create(0.05);
        local delay3 = cc.DelayTime:create(0.13 * (len - i) + 0.2);
        local scaleTo1 = cc.EaseIn:create(cc.ScaleTo:create(0.6, 2.2), 5.5);
        local scaleTo2 = cc.ScaleTo:create(0.2, 1.1);
        local scaleTo3 = cc.ScaleTo:create(0.1, 1.3);

        --[[local moveOutLen = AlignM.frameSize.width / 2;
        if node:getName() == "attack_node" then
            moveOutLen = -1 * moveOutLen;
        end
        local moveOut = cc.EaseIn:create(cc.MoveBy:create(0.15, cc.p(moveOutLen, 0)), 2.5);
        local moveOut2 = cc.MoveBy:create(0.1, cc.p(moveOutLen / -30, 0));]]

        local scaleTo4 = cc.ScaleTo:create(0.6, 2);
        local fadeOut = cc.FadeOut:create(0.6);

        --text:runAction(cc.Sequence:create(delay1, callFunc1, scaleTo1, scaleTo2, scaleTo3, delay3, moveOut2, moveOut, callFunc2));
        text:runAction(cc.Sequence:create(delay1, callFunc1, scaleTo1, scaleTo2, scaleTo3, delay3, cc.Spawn:create(fadeOut, scaleTo4), callFunc2));
    end
end

-- 受创摇晃特效
function UISkyCityCombat:slimeShake(node, delay)
    local offset = 4;
    local angle = -20;


    local moveRight = cc.MoveBy:create(0.1, cc.p(offset, 0));
    local rotateTo = cc.RotateTo:create(0.3, angle, angle);
    local delayTime = cc.DelayTime:create(delay or 0);
    local moveBack = cc.MoveBy:create(0.05, cc.p(-offset, 0));
    local rotateBack = cc.RotateTo:create(0.05, 0, 0);

    local initScaleX = node:getScaleX();
    local initScaleY = node:getScaleY();
    local scaleDown = cc.ScaleTo:create(0.1, 0.9 * initScaleX, 0.9 * initScaleY);
    local scaleUp = cc.ScaleTo:create(0.1, initScaleX, initScaleY);

    --local action = cc.Sequence:create(cc.Spawn:create(moveRight, rotateTo), delay, cc.Spawn:create(moveBack, rotateBack));

    local moveRight = cc.MoveBy:create(0.05, cc.p(offset, 0));
    local moveUp = cc.MoveBy:create(0.05, cc.p(0, offset));
    local moveLeft = cc.MoveBy:create(0.05, cc.p(-offset, 0));
    local moveDown = cc.MoveBy:create(0.05, cc.p(0, -offset));

    local action = cc.Sequence:create(delayTime, moveLeft, moveDown, moveRight, moveUp);
    node:runAction(action);
end

-- 播放冈布奥受创动作
function UISkyCityCombat:playPetDamagedAction(list, style)
    -- 计算需要后仰的数量
    local totalNum = #list;
    local num = math.random(math.floor(totalNum / 5), math.floor(totalNum / 2));

    if num <= 1 then
        num = math.min(#list, 1);
    end

    -- 随机抽取要击飞的冈布奥
    local numList = randomArray(totalNum, num);
    local petList = {};

    for _, index in ipairs(numList) do
        table.insert(petList, list[index]);
    end

    -- 击飞后跳回来
    local len = -150;

    for _, icon in ipairs(petList) do
        if not icon.effectSetting or icon.effectSetting["no_damage"] ~= 1 then
            if style == 2 and icon.isMonster then
                len = 150;
            else
                len = -150;
            end

            local randomValue = math.random(150);
            local delay = cc.DelayTime:create(randomValue / 300);
            local jumpTo1 = cc.JumpBy:create(1, cc.p(len, 0), math.random(80, 100), 1);
            local jumpTo2 = cc.JumpBy:create(0.5, cc.p(-1 * len, 0), 20, math.random(2, 3));

            icon:runAction(cc.Sequence:create(delay, jumpTo1, jumpTo2));

            local monsterId = icon.petId;
            if icon.isMonster == true and MonsterM.query(monsterId, "monster_type") ~= MONSTER_TYPE_BOSS
                or DungeonBossM.isInBossLayer() then
                -- 如果是怪物的话，投影也要跟着移动
                local index = icon.index;
                local parent = icon:getParent();

                local shadow = parent:getChildByName("shadow" .. index);
                if shadow then
                    local delay2 = cc.DelayTime:create(randomValue / 300);
                    local moveBy1 = cc.MoveBy:create(1, cc.p(len, 0));
                    local moveBy2 = cc.MoveBy:create(0.5, cc.p(-1 * len, 0));

                    shadow:runAction(cc.Sequence:create(delay2, moveBy1, moveBy2));
                end
            end
        end
    end
end

-- 播放属性触发特效
function UISkyCityCombat:combatPropEffect(para)
    local source = para.source;
    local args = para.args;

    local style = source:getStyle();
    local armyNode;
    if style == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
    else
        -- 伤害方
        armyNode = findChildByName(self.node, "defense_node");
    end

    -- 逐一播放
    for index, info in ipairs(args) do
        performWithDelay(armyNode, function ()
            self:playPropEffect(source, info);
        end, index - 1);
    end

    -- 刷新属性
    self:updateAttrib(source);
end

-- 播放属性触发特效
function UISkyCityCombat:playPropEffect(fighter, para)
    local effectType    = para["type"];
    local damage        = para["damage"];

    local style = fighter:getStyle();
    local armyNode;
    if style == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
    else
        -- 伤害方
        armyNode = findChildByName(self.node, "defense_node");
    end

    self:playNumberEffect(armyNode, damage, "damage");

    self:playSkyCombatEffect(fighter, effectType);
    --[[if effectType == "ship_burn" then
        -- 燃烧特效
        self:playBurnEffect(fighter);
    elseif effectType == "ship_poisoned" then
        -- 中毒
        self:playDebuffEffect({["target"] = fighter});
    else
        -- 其他未处理特效
        print("触发未处理的特效 " .. effectType);
    end]]
end

-- 播放下燃烧特效
function UISkyCityCombat:playBurnEffect(fighter)
    local armyNode;

    if fighter:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
        playEffect(armyNode, 2003, 316, -300);
    else
        armyNode = findChildByName(self.node, "defense_node");
        playEffect(armyNode, 2003, -316, -300);
    end

    -- 史莱姆或者怪物的表现
    if type(armyNode.islandType) == "string" then
        -- 怪物

    else
        -- 史莱姆
        --self:bloodEffect(fighter, 1, 7);
    end
end

-- 播放刷新怪物
function UISkyCityCombat:playRefreshMonsterEffect(para)
    local fighter = para["source"];

    self:dieEffect();

    -- 出现新的怪物，并且刷时空裂痕的属性(替换新的属性)
    local function newDefenser()
        self.defenser = fighter;

        self:collectData();

        self:redrawDeffenser();

        self:updateAttrib(self.defenser);
        self:updateAttrib(self.attacker);
    end

    local fighterInfo = self.data["defenser"];

    local pets = fighterInfo["pets"];
    local delay = (#pets) * 0.2 + 0.5;
    performWithDelay(self, newDefenser, delay);
end

-- 播放怪物死亡光效
function UISkyCityCombat:dieEffect()
    -- 原来旧的怪物干掉
    local defenseNode = findChildByName(self.node, "defense_node");

    local petNode = defenseNode.petNode;

    -- 根据旧的怪物信息，删除对应的点
    local fighterInfo = self.data["defenser"];

    local pets = fighterInfo["pets"];

    for index = 1, #pets do
        local function doDieEffect()
            local shadowImg = petNode:getChildByName("shadow" .. index);
            if shadowImg then
                petNode:removeChild(shadowImg);
            end

            local monster = findChildByName(petNode, "monster" .. index);

            playShakeEffect2(monster, 0.05, 5, 3, cc.p(monster:getPosition()));

            -- 使用默认的死亡动画
            -- 创建死亡光效节点
            local function playDieAimation()
                local effectNode = cc.Node:create();

                effectNode:setPosition(monster:getPositionX(), monster:getPositionY() - 20);

                local monsterPos = monster:getPositionX();

                effectNode:setName("die_node" .. index);
                petNode:addChild(effectNode);

                local path = "animation/die_bomb.csb";
                local effect = cc.CSLoader:createNode(path);
                local animAction = cc.CSLoader:createTimeline(path);
                effect:runAction(animAction);
                effectNode:addChild(effect);
                animAction:gotoFrameAndPlay(0, 14, false);

                local function callback()
                    local dieNode = petNode:getChildByName("die_node" .. index);
                    if dieNode then
                        petNode:removeChild(dieNode);
                    end
                end

                performWithDelay(petNode, callback, 0.3);

                if monster ~= nil then
                    monster:removeAllChildren();

                    monster.petId = nil;
                    monster.index = nil;
                    monster.isMonster = nil;
                    monster.display = nil;
                    if monster.effectSetting and 1 == monster.effectSetting.die_show_insland then
                        self:showInsland(defenseNode, true);
                        self:defenserFlyInEffect(defenseNode);
                    end
                    monster.effectSetting = nil;
                end
            end
            performWithDelay(monster, playDieAimation, 0.2);
        end

        performWithDelay(petNode, doDieEffect, (index - 1) * 0.2);
    end
end

-- 重绘防守方怪物
function UISkyCityCombat:redrawDeffenser()
    -- 绘制史莱姆或怪物
    self.defenserList = {};
    self.monsterPosList = {};

    local fighterInfo = self.data["defenser"];
    local armyNode = findChildByName(self.node, "defense_node");

    if self.delayAppear == true then
        -- 按钮出现
        local function btnAppear()
            self:btnBackAppear();
        end

            -- 按钮出现
        local pets = fighterInfo["pets"];
        local delay = (#pets) * 0.5;
        performWithDelay(self.node, btnAppear, delay);
        self.delayAppear = false;
    end

    for index = 1, MAX_PET_NUM do
        -- 绘制史莱姆
        local monster = self:redrawMonster(armyNode, index, fighterInfo["pets"][index], fighterInfo);

        if monster then

            self:monsterAppearEffect(armyNode, monster, index);

            -- 防守方列表
            table.insert(self.defenserList, monster);
        end
    end
end

-- 怪物在中点飞到各个对应的节点
function UISkyCityCombat:monsterAppearEffect(armyNode, monster, index)
    if monster then
        local pos = cc.p(monster:getPositionX(), monster:getPositionY());

        monster:setScale(0.01);

        monster:setOpacity(0);

        local scaleIndex = index % 3;
        local scaleCef = 1;
        if scaleCef == 0 then
            scaleCef = 0.8;
        elseif scaleCef == 1 then
            scaleCef = 0.9;
        end

        local scaleUp = cc.ScaleTo:create(0.2, 1.6);
        local scaleUp2 = cc.ScaleTo:create(0.2, 1.6);
        local fadeIn = cc.FadeIn:create(0.3);
        local delay = cc.DelayTime:create(0.35 * index);
        local delay2 = cc.DelayTime:create(0.35 * index);
        local scaleDown = cc.ScaleTo:create(0.15, 1 * 1.25 * scaleCef);
        -- local scaleDown2 = cc.ScaleTo:create(0.15, 1 * 1.25 * scaleCef);

        monster:runAction(cc.Sequence:create(delay2, fadeIn));
        monster:runAction(cc.Sequence:create(delay, scaleUp2, scaleDown));

        local shadowImg = armyNode.petNode:getChildByName("shadow" .. index);
        shadowImg:setOpacity(0);
        local fadeIn = cc.FadeIn:create(0.01);

        local delay = cc.DelayTime:create(0.2 * index);
        shadowImg:runAction(cc.Sequence:create(delay, fadeIn));
    end
end

-- 需要修改当前层级
function UISkyCityCombat:sortZnode()
    local fighterInfo = self.data["defenser"];
    if fighterInfo["islandType"] ~= "Whirlpool" and
        fighterInfo["relic_boss"] ~= true and
        fighterInfo["island_boss"] ~= true then
        return;
    end

    local skyNode = findChildByName(self.node, "sky");

    local attackNode    = findChildByName(self.node, "attack_node");
    local bossCloudEffect = findChildByName(self.node, "boss_cloud");
    local defenseNode   = findChildByName(self.node, "defense_node");
    local attackEffect  = findChildByName(self.node, "attack_effect");
    local defenseEffect = findChildByName(self.node, "defense_effect");
    local skyEffectNode = findChildByName(self.node, "sky_effect");

    local defenseLocZ = defenseNode:getLocalZOrder();

    bossCloudEffect:setLocalZOrder(defenseLocZ + 1);
    attackNode:setLocalZOrder(defenseLocZ + 1);
    attackEffect:setLocalZOrder(defenseLocZ + 1);
    defenseEffect:setLocalZOrder(defenseLocZ + 1);
    skyEffectNode:setLocalZOrder(defenseLocZ + 1);
end

-- 蒸汽坦克自己的站位
function UISkyCityCombat:updateTankPosition(monsterNode, index)
    if index == 4 then
        -- 四号站位修正一下
        monsterNode:setPosition(cc.p(-120, 570));
    end
end

-- 时空裂痕的每波怪数量位置是不同的
function UISkyCityCombat:updateMonsterPosition(monsterNode, index)
    local fighterInfo = self.data["defenser"];
    if fighterInfo["islandType"] ~= "Whirlpool" then
        if fighterInfo["steam_machine"] and fighterInfo["steam_machine"] == "steam_tank" then
            -- 蒸汽坦克，有自己的站位
            self:updateTankPosition(monsterNode, index);
        end
        return;
    end

    local armyAmount = #table.keys(fighterInfo["pets"]);

    -- 根据怪物的数量获得怪物的站位
    local monster3 = {[1] = {["x"] = 50, ["y"] = 160}, [2] = {["x"] = 45, ["y"] = -255}, [3] = {["x"] = -50, ["y"] = -50}};

    local monster4 = {[1] = {["x"] = -20, ["y"] = 160}, [2] = {["x"] = -100, ["y"] = -70},
                [3] = {["x"] = 0, ["y"] = 60}, [4] = {["x"] = 20, ["y"] = -265},};

    local monster5 = {[1] = {["x"] = -45, ["y"] = -125}, [2] = {["x"] = 90, ["y"] = -175},
                [3] = {["x"] = 40, ["y"] = 20}, [4] = {["x"] = 0, ["y"] = -320},[5] = {["x"] = 0, ["y"] = 160},};

    local pos;
    if armyAmount == 1 then
        pos = {["x"] = 0, ["y"] = -70};
    elseif armyAmount == 3 then
        pos = monster3[index];
    elseif armyAmount == 4 then
        pos = monster4[index];
    else
        pos = monster5[index];
    end

    if pos == nil then
        return;
    end

    local scaleIndex = index % 3;
    local scaleCef = 1;
    if scaleCef == 0 then
        scaleCef = 0.8;
    elseif scaleCef == 1 then
        scaleCef = 0.9;
    end

    monsterNode:setScale(1 * 1.25 * scaleCef);

    monsterNode:setPosition(cc.p(pos.x, pos.y));
end

-- 加载对应的时空裂痕背景
function UISkyCityCombat:updateWhirlpoolBg(node)
    -- 当前出战阵营是否与时空裂痕对应
    local camp = ME.user.dbase:query("activedSkyGroup");

    local machineEffect = findChildByName(node, "machine_effect");

    local effectBg = findChildByName(machineEffect, "effect_bg");
    local effectBg2 = findChildByName(machineEffect, "effect_bg2");

    local bgIcon = "camp_%d_%d";
    effectBg:loadTexture(getWhirlpoolIcon(string.format(bgIcon, camp, 1)));

    effectBg2:loadTexture(getWhirlpoolIcon(string.format(bgIcon, camp, 2)));

    -- 修改光晕
    local machineHalo = findChildByName(node, "machine_halo");

    local machineHalo1 = findChildByName(machineHalo, "mahine_halo");
    local machineHalo2 = findChildByName(machineHalo, "mahine_halo2");

    local haloPath = getWhirlpoolIcon(string.format("camp_halo_%d", camp));

    machineHalo1:loadTexture(haloPath);
    machineHalo2:loadTexture(haloPath);
end

-- 针对csb文件做变红处理
function UISkyCityCombat:displayBloodEffect(target, index, total)
    if index > total then
        return;
    end

    local childList = getAllChilds(target);

    for _, child in pairs(childList) do
        -- 图片变红
        setRedMode(child, index % 2 == 1);
    end

    -- 还原
    performWithDelay(self, function ()
        self:displayBloodEffect(target, index + 1, total);
    end, 0.2);
end

-- 播放boss云层效果
function UISkyCityCombat:playSkyBossCloud()
    if not self.defenser.dbase:query("relic_boss") and not self.defenser.dbase:query("island_boss") then
        return;
    end

    local bossCloudEffect = findChildByName(self.node, "boss_cloud");
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    math.randomseed(tostring(os.time()):reverse():sub(1, 6));

    --
    local firstCloud = true;
    local function cloudEffect()
        local cloudNum = math.random(2, 3);

        bossCloudEffect:removeAllChildren();

        for i=1, cloudNum do
            local icon = ccui.ImageView:create();
            icon:loadTexture(getSkyCityPath("sky/floating_cloud" .. math.random(2)), 1);

            local moveBy;
            if firstCloud then
                icon:setPosition(cc.p(w / 2 - math.random(150, 200), math.random(- 200,  100)));
                local direction = -1;

                if math.random(2) < 2 then
                    direction = 1;
                end

                moveBy = cc.MoveBy:create(math.random(25, 35), cc.p(direction * w, 0));
            else
                icon:setPosition(cc.p(w / 2 + math.random(150, 200), math.random(- 200,  100)));
                moveBy = cc.MoveBy:create(math.random(20, 30), cc.p(-w, 0));
            end

            local delay = cc.DelayTime:create(0.1);

            icon:runAction(cc.Sequence:create(delay, moveBy));

            bossCloudEffect:addChild(icon);
        end

        firstCloud = false;

        performWithDelay(bossCloudEffect, cloudEffect, math.random(10, 60));
    end

    cloudEffect();
end

-- 播放boss出场效果
function UISkyCityCombat:bossApearEffect(monsterNode)
    -- boss位置出现虚影
    local function playSkyBossEffect()
        local monster = cc.Node:create();

        local worldPos = monsterNode:convertToWorldSpaceAR(cc.p(0, 0));

        local bossCloudNode = findChildByName(self.node, "boss_cloud");
        local sourcePos = bossCloudNode:convertToNodeSpace(worldPos);
        monster:setPosition(sourcePos);
        bossCloudNode:addChild(monster);

        -- 预先放置图片
        local imageList = {};
        local function createImage(index)
            local image = ccui.ImageView:create();

            image:loadTexture(getSkyBossImg(monsterNode.petId));

            print(getSkyBossImg(monsterNode.petId));
            image:setAnchorPoint(cc.p(0.5, 0.5));
            image:setScale(0.9);
            image:setOpacity(255 * 0.9 / index);
            monster:addChild(image);
            image:setPosition(40, 60);
            table.insert(imageList, image);
        end

        createImage(1);
        createImage(2);

        -- 英雄位置出现一个虚影，放大
        local index = 1;
        local function playEffect()
            local image = imageList[index];

            local duration = 0.5;
            local scaleUp = cc.ScaleTo:create(duration, 1.5 * image:getScale());
            local fadeOut = cc.FadeTo:create(duration, 0);
            local moveUp = cc.MoveBy:create(duration, cc.p(0, 60));
            local callFunc = cc.CallFunc:create(function()
                image:removeFromParent();
            end);
            image:runAction(cc.Sequence:create(cc.Spawn:create(scaleUp, fadeOut, moveUp), callFunc));

            index = index + 1;
        end

        playEffect();
        performWithDelay(monster, playEffect, 0.3);
    end

    -- 播放虚影
    local callback = cc.CallFunc:create(function()
        playSkyBossEffect();
    end);

    -- boss由小到大再到原值
    local initScale = monsterNode:getScale();
    monsterNode:setScale(initScale * 0.8);

    local scaleUp = cc.ScaleTo:create(0.3, initScale);

    local delay = cc.DelayTime:create(0.2);

    monsterNode:runAction(cc.Sequence:create(scaleUp, delay, callback));
end

-- 播放时空裂痕浮岛
function UISkyCityCombat:playWhirlpoolCampEffect()
    local defenseNode   = findChildByName(self.node, "defense_node");

    local islandNode = findChildByName(defenseNode, "island_node");
    islandNode:setPositionY(0);

    local WhirlpoolNode = findChildByName(islandNode, "islandType");

    local effectNode = findChildByName(WhirlpoolNode, "machine_effect/effect");
    local effectNode2 = findChildByName(WhirlpoolNode, "machine_effect/effect2");

    whirlpoolEffect(effectNode, 1723, -435, 489);
    playEffect(effectNode2, 1724, -454, 488);
end

-- 天空活动战斗，询问是否复活
function UISkyCityCombat:askToRevive()
    local desc;
    local reviveCost = FormulaM.invoke("CALC_SC_ACTIVITY_REVIVE_GEM_COST", self.revive);
    local function cancelCallBack()
        -- 关闭战斗结果界面的时候也关闭战斗界面
        closeFormByName("UISkyCityCombat");
    end

    -- 判断能否复活
    local maxReviveTimes = FormulaM.invoke("CALC_SC_ACTIVITY_REVIVE_MAX_TIMES");

    if type(self.revive) == "number" and
        self.revive >= maxReviveTimes then

        -- "花费%d钻石复活，继续战斗！\n[blue]（剩余复活次数：%d/%d）[-]",
        desc = string.format(getLocStr("sky_activity_revive_desc"), reviveCost, maxReviveTimes - self.revive, maxReviveTimes);
        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("slime_note"), desc,
            cancelCallBack, nil, getLocStr("leave_combat"));
        return;
    end

    local function okCallBack()
        -- 玩家选择了复活

        local function leaveCombat()
            -- 关闭战斗结果界面的时候也关闭战斗界面
            closeFormByName("UISkyCityCombat");
        end

        local activityInfo = ActivityM.getActivityInfo(self.activityId);
        local status = ActivityM.getActivityStatus(activityInfo);
        local combatStatus = SkyActivityM.getCombatStatus(self.activityId, self.combatId);

        local activityData  = SkyActivityM.getActivityData(self.activityId);
        local combat        = activityData["combat"] or {};
        local combatData    = combat[self.combatId] or {};

        if status == ACTIVITY_STATUS_END or status == ACTIVITY_STATUS_CLOSE then
            -- 活动已结束，提示后离开战斗
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("activity_end"),
                leaveCombat, nil, getLocStr("leave_combat"), nil, {["hideBackBtn"] = true});
            return;
        elseif #table.keys(combatData) <= 0 then
            alert(getLocStr("sky_activity_combat_reseted"));
            leaveCombat();
            return;
        elseif combatStatus == "lock" then
            alert(SkyActivityM.queryCombat(self.combatId, "condition_tip"));
            leaveCombat();
            return;
        elseif combatStatus == "is_die" then
            alert(getLocStr("sky_activity_die_tip"));
            leaveCombat();
            return;
        elseif ME.user.dbase:query("gem", 0) < reviveCost then
            -- 钻石不足，提示后离开战斗
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("gem_lack_cannot_recive"),
                leaveCombat, nil, getLocStr("leave_combat"), nil, {["hideBackBtn"] = true});
            return;
        end

        -- 发消息给服务器
        Operation.cmd_join_sc_activity(self.activityId, { ["combat_id"] = self.combatId, ["is_revive"] = 1, });

        -- 显示 "加载中" 界面
        showCommunicatingForm(getLocStr("loading"));
        -- 关闭战斗结果界面的时候也关闭战斗界面
        closeFormByName("UISkyCityCombat");
    end

    -- 弹出复活提示框
    -- "花费%d钻石复活，继续战斗！\n[blue]（剩余复活次数：%d/%d）[-]",
    desc = string.format(getLocStr("sky_activity_revive_desc"), reviveCost, maxReviveTimes - self.revive, maxReviveTimes);
    confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), desc,
        okCallBack, cancelCallBack, getLocStr("btn_text_revive"), nil, {["hideBackBtn"] = true});
end

-- 播放天空战效果
function UISkyCityCombat:playSkyCombatEffect(fighter, effect)
    local effectConfig  = SkyCombatM.queryEffect(effect);
    local effectId      = effectConfig["effect_id"];
    local offset        = effectConfig["offset"];
    local scale         = effectConfig["scale"];
    local armyNode;
    local ratio = 1;

    if not effectId or not offset then
        return;
    end

    if fighter:getStyle() == "attacker" then
        -- 攻击方
        armyNode = findChildByName(self.node, "attack_node");
    else
        armyNode = findChildByName(self.node, "defense_node");

        ratio = -1;
    end

    playEffect(armyNode, effectId, ratio * offset[1], offset[2], nil, scale);
end

-- 攻击方前进到防守方处
function UISkyCityCombat:defenserFlyInEffect(defenseNode)
    defenseNode.initX = defenseNode:getPositionX();
    defenseNode:setPositionX(1 * defenseNode.initX + DESIGN_WIDTH);

    -- 上下浮动
    playFluctuateAnima(defenseNode, 0.05, 2);

    local moveDelay = 2;
    local w = cc.Director:getInstance():getOpenGLView():getFrameSize().width;

    local moveTo1 = cc.EaseInOut:create(cc.MoveTo:create(2.5, cc.p(defenseNode.initX, defenseNode:getPositionY())), 2.5);
    local callFunc = cc.CallFunc:create(function ()
        defenseNode:stopAllActions();

        -- 开始正常的浮动效果
        self:playFluctuateEffect();
    end);

    defenseNode:runAction(cc.Sequence:create(moveTo1, callFunc));

    -- 屏幕移动
    local skyNode = findChildByName(self.node, "sky");
    local skyBg = findChildByName(self.node, "sky/sky_bg");

    local moveBy1 = cc.MoveBy:create(moveDelay, cc.p(-1.3 * w, 0));
    local moveBy2 = cc.MoveBy:create(moveDelay, cc.p(1.3 * DESIGN_WIDTH, 0));

    skyNode:runAction(cc.Sequence:create(moveBy1));
    skyBg:runAction(cc.Sequence:create(moveBy2));
end